using Fantasy.SourceGenerator.Common;
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Fantasy.SourceGenerator.Generators
{
    /// <summary>
    /// 数据库职责枚举生成器
    /// 从 Fantasy.config 中解析 database 配置，自动生成 DatabaseDuty 枚举
    /// </summary>
    [Generator]
    public class FantasyConfigGenerator : IIncrementalGenerator
    {
        private const string SourceItemGroupMetadata = "build_metadata.AdditionalFiles.SourceItemGroup";

        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            // 查找 Fantasy.config 文件，使用 SourceItemGroup 元数据进行过滤
            var configFiles = context.AdditionalTextsProvider
                .Combine(context.AnalyzerConfigOptionsProvider)
                .Where(static pair =>
                {
                    var (file, configOptions) = pair;

                    // 检查文件名是否以 Fantasy.config 结尾
                    if (!file.Path.EndsWith("Fantasy.config", System.StringComparison.OrdinalIgnoreCase))
                    {
                        return false;
                    }

                    // 检查 SourceItemGroup 元数据，确保是 FantasyConfig
                    // 如果没有元数据，为了向后兼容也接受（允许手动配置 AdditionalFiles 的场景）
                    var options = configOptions.GetOptions(file);
                    if (options.TryGetValue(SourceItemGroupMetadata, out var sourceItemGroup))
                    {
                        // 如果有元数据，必须是 FantasyConfig
                        return sourceItemGroup == "FantasyConfig";
                    }

                    // 没有元数据，接受所有 Fantasy.config 文件（向后兼容）
                    return true;
                })
                .Select(static (pair, cancellationToken) =>
                {
                    var (file, _) = pair;
                    return file.GetText(cancellationToken)?.ToString();
                })
                .Collect();

            // 组合编译信息和配置文件
            var compilationAndConfig = context.CompilationProvider.Combine(configFiles);

            // 注册源代码输出
            context.RegisterSourceOutput(compilationAndConfig, static (spc, source) =>
            {
                if (CompilationHelper.IsSourceGeneratorDisabled(source.Left))
                {
                    return;
                }

                if (!CompilationHelper.HasFantasyNETDefine(source.Left))
                {
                    return;
                }

                var configs = source.Right;
                GenerateCode(spc, source.Left, configs.Length == 0 ? null : configs[0]);
            });
        }

        private static void GenerateCode(SourceProductionContext context, Compilation compilation, string? configContent)
        {
            try
            {
                var config = GetConfig(configContent);
                // 获取当前程序集名称（仅用于注释）
                var markerClassName = compilation.GetAssemblyName("FantasyConfigRegistrar", out var assemblyName, out _);
                // 生成代码文件
                var builder = new SourceCodeBuilder();
                // 添加文件头
                builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
                // 添加 using
                builder.AddUsings(
                    "System",
                    "System.Collections.Generic",
                    "Fantasy.Assembly"
                );
                builder.AppendLine();
                // 开始命名空间
                 builder.BeginDefaultNamespace();
                // 开始类定义（实现 IFantasyConfigRegistrar 接口）
                builder.AddXmlComment($"Auto-generated Entity System registration class for {assemblyName}");
                builder.BeginClass(markerClassName, "internal sealed", "global::Fantasy.Assembly.IFantasyConfigRegistrar");
                builder.BeginMethod("public Dictionary<string, int> GetDatabaseNameDictionary()");
                builder.AppendLine("var dbNameDictionary = new Dictionary<string, int>();");
                if (config is { ConfigContent: true, Root: not null })
                {
                    var databaseNameHashSet = new HashSet<string?>();
                    var databaseNameElements =
                        config.Root.Element(config.ns + "server")
                            ?.Element(config.ns + "worlds")
                            ?.Elements(config.ns + "world")
                            .SelectMany(w => w.Elements(config.ns + "database"))
                            .Select(d => new
                            {
                                DbName = d.Attribute("dbName")?.Value
                            }).Where(d => d?.DbName != null && !string.IsNullOrEmpty(d.DbName)).ToList();
                    if (databaseNameElements != null && databaseNameElements.Any())
                    {
                        foreach (var element in databaseNameElements)
                        {
                            databaseNameHashSet.Add(element.DbName);
                        }
                        
                        GenerateDbNameConstCode(context, databaseNameHashSet, builder);
                    }
                }
                builder.AppendLine("return dbNameDictionary;");
                builder.EndMethod();
                builder.BeginMethod("public Dictionary<string, int> GetSceneTypeDictionary()");
                builder.AppendLine("var sceneTypeDictionary = new Dictionary<string, int>();");
                if (config is { ConfigContent: true, Root: not null })
                { 
                    var sceneTypeHashSet = new HashSet<string?>();
                    var sceneTypeElements =
                        config.Root.Element(config.ns + "server")
                            ?.Element(config.ns + "scenes")
                            ?.Elements(config.ns + "scene")
                            .Select(d => new
                            {
                                SceneTypeString = d.Attribute("sceneTypeString")?.Value
                            }).Where(d => d?.SceneTypeString != null && !string.IsNullOrEmpty(d.SceneTypeString)).ToList();
                    if (sceneTypeElements != null && sceneTypeElements.Any())
                    {
                        foreach (var element in sceneTypeElements)
                        {
                            sceneTypeHashSet.Add(element.SceneTypeString);
                        }
                        GenerateSceneTypeConstCode(context, sceneTypeHashSet, builder);
                    }
                }
                builder.AppendLine("return sceneTypeDictionary;");
                builder.EndMethod();
                // 结束类和命名空间
                builder.EndClass();
                builder.EndNamespace();
                context.AddSource($"{markerClassName}.g.cs", builder.ToString());
            }
            catch
            {
                // 解析失败时静默忽略，不影响编译
            }
        }

        private static void GenerateDbNameConstCode(SourceProductionContext context, HashSet<string?> databaseNameHashSet,SourceCodeBuilder dbNameDicBuilder)
        {
            var builder = new SourceCodeBuilder();
            builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
            builder.AddUsings(
                "System",
                "System.Collections.Generic",
                "Fantasy.Assembly"
            );
            builder.AppendLine();
            builder.BeginNamespace("Fantasy.Database");
            builder.AddXmlComment("Retrieve the database name constant (automatically generated from Fantasy.config)");
            builder.BeginClass("DatabaseName", "public static", "");
            var index = 0;
            foreach (var dbName in databaseNameHashSet)
            {
                if (dbName == null)
                {
                    continue;
                }
                builder.AddXmlComment(dbName);
                builder.AppendLine($"public const int {dbName.ToPascalCase()} = {index};");
                dbNameDicBuilder.AppendLine($"dbNameDictionary.Add(\"{dbName}\", {index});");
                index++;
            }
            builder.EndClass();
            builder.EndNamespace();
            context.AddSource("DatabaseName.g.cs", builder.ToString());
        }
        
        private static void GenerateSceneTypeConstCode(SourceProductionContext context, HashSet<string?> sceneTypeNameHashSet,SourceCodeBuilder sceneTypeDicBuilder)
        {
            var builder = new SourceCodeBuilder();
            builder.AppendLine(GeneratorConstants.AutoGeneratedHeader);
            builder.AddUsings(
                "System",
                "System.Collections.Generic",
                "Fantasy.Assembly"
            );
            builder.AppendLine();
            builder.BeginNamespace("Fantasy");
            builder.AddXmlComment("Retrieve the SceneType constant (automatically generated from Fantasy.config)");
            builder.BeginClass("SceneType", "public static", "");
            var index = 0;
            foreach (var sceneType in sceneTypeNameHashSet)
            {
                if (sceneType == null)
                {
                    continue;
                }
                builder.AddXmlComment(sceneType);
                builder.AppendLine($"public const int {sceneType.ToPascalCase()} = {++index};");
                sceneTypeDicBuilder.AppendLine($"sceneTypeDictionary.Add(\"{sceneType}\", {index});");
            }
            builder.EndClass();
            builder.EndNamespace();
            context.AddSource("SceneType.g.cs", builder.ToString());
        }
        
        private static (bool ConfigContent, XNamespace? ns, XElement? Root) GetConfig(string? configContent)
        {
            if (configContent == null)
            {
                return (false, null, null);
            }

            var doc = XDocument.Parse(configContent);
            var ns = XNamespace.Get("http://fantasy.net/config");

            return doc.Root != null ? (true, ns, doc.Root) : (false, null, null);
        }
    }
}
